import Lexer from '../lexer'
import Parser from '../parser/parser'

import sign, {
  allowBlockBody,
  isAssign,
  isInfix,
  isOperand,
  isOperator,
  isSuffix,
  keyOf
} from '../ast/sign'

function ignore (right) {
  return  {
    sign: 'const',
    body: [
      {
        sign: '=',
        left: {sign: '_'},
        right: right
      }
    ]
  }
}

function trim (decl) {
  let dist = {}
  Object.keys(decl).forEach(k => {
    if (k === 'sign')
      dist.sign = keyOf(decl.sign)
    else if (k !== 'start' && k !== 'end' && k !== 'body')
      dist[k] = decl[k]
  })
  if (decl.body)
    dist.body = decl.body.map(decl => trim(decl))
  return dist
}

function tranform (node) {
  let dist = {}

  if (node.left)
    dist.left = tranform(node.left)

  dist.sign = keyOf(node.sign)

  if (node.right)
    dist.right = tranform(node.right)

  if (
    node.body
    && node.sign !== sign.template
    && node.sign !== sign.regex
    && node.sign !== sign.string
  ) {
    dist.body = node.body.map(tranform)
  }

  return dist
}

function fmt (node) { // eslint-disable-line
  echo(tranform(node))
}

function echo (goal) { // eslint-disable-line
  console.log( // eslint-disable-line
    JSON.stringify(goal, null, '  ')
  )
}

function si (sign) {
  return sign.sign && sign || {sign}
}

function lr (left, sign, right, body) {
  left = si(left)
  right = si(right)
  if (body) {
    body = body.map(si)
    return {left, sign, right, body}
  }

  return {left, sign, right}
}
function sr (sign, right, body) {
  right = si(right)
  if (body) {
    body = body.map(si)
    return {sign, right, body}
  }
  return {sign, right}
}
function sl (sign, left, body) {
  left = si(left)
  if (body) {
    body = body.map(si)
    return {sign, left, body}
  }
  return {sign, left}
}
function sb (sign, body, right) {
  if (typeof right === 'string')
    right = si(right)
  body = body.map(si)
  return right && {sign, body, right} ||
      {sign, body}
}

describe('sign is', () => {
  it('is', () => {
    expect(isAssign(sign['='])).to.be.equal(true)
    expect(isAssign(sign['~='])).to.be.equal(true)
    expect(isInfix(sign[','])).to.be.equal(true)
    expect(isInfix(sign['.'])).to.be.equal(true)
    expect(isInfix(sign['--'])).to.be.equal(false)
    expect(isInfix(sign['++'])).to.be.equal(false)
    expect(isSuffix(sign['--'])).to.be.equal(true)
    expect(isSuffix(sign['++'])).to.be.equal(true)
    expect(isOperand(sign.ident)).to.be.equal(true)
    expect(isOperand(sign['()'])).to.be.equal(true)
    expect(isOperand(sign['[]'])).to.be.equal(true)
    expect(isOperand(sign['{}'])).to.be.equal(true)
    expect(isOperator(sign['_'])).to.be.equal(false)

    expect(allowBlockBody(sign.fn)).to.be.equal(true)
  })
})

describe('just comments', () => {
  it('nothing', () => {
    let
      src = '',
      goal = [],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal(goal)
    expect(parse.lines).to.deep.equal([0])
  })

  it('empty line', () => {
    let
      src = '\n',
      goal = [],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal(goal)
    expect(parse.lines).to.deep.equal([0, 0])
  })

  it('empty lines', () => {
    let
      src = '\n\n',
      goal = [],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal(goal)
    expect(parse.lines).to.deep.equal([0, 0, 0])
  })

  it('single comment', () => {
    let
      src = '===',
      goal = [],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal(goal)
    expect(parse.lines).to.deep.equal([0, 0])
  })

  it('single comment and newline', () => {
    let
      src = '===\n',
      goal = [],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal(goal)
    expect(parse.lines).to.deep.equal([0, 0])
  })

})

describe('simple tokens', () => {
  it('-x and _', () => {
    let
      src = `const
      _, x = -1 * ;note
        2,
  ;note
      -3 + iota++
  ---
  notes
  ---
      _, x = -1 * 2, -3 + iota++`,
      lines = [
        0, 1, 7, 9, 0, 7, 0, 0, 0, 7
      ],
      goal = [
        {sign: 'const'},
        {sign: '_'},
        {sign: ','},
        {sign: 'ident', data: 'x'},
        {sign: '='},
        {sign: '-x'},
        {sign: 'i8', data: 1},
        {sign: '*'},
        {sign: 'i8', data: 2},
        {sign: ','},
        {sign: '-x'},
        {sign: 'i8', data: 3},
        {sign: '+'},
        {sign: 'iota'},
        {sign: '++'},
      ],
      parse = new Parser(new Lexer(src)),
      body = parse.tokens()

    goal.push(...goal.slice(1))

    expect(parse.error).to.be.equal(null)
    expect(parse.lines).to.deep.equal(lines)
    expect(body.map(trim)).to.deep.equal(goal)

    body = parse.tokens()

    expect(parse.error).to.be.equal(null)
    expect(body).to.deep.equal([])

  })
})

describe('expression', () => {

  it('x + y + z', () => {
    let
      src = 'const _ = 1 + true + false',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = lr(
        lr('i8', '+', 'true'),
        '+',
        'false'
      )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node.right.right)).to.deep.equal(goal)
  })

  it('x.y.z', () => {
    let
      src = 'const _ = null.true.1',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = lr(
        lr('null', '.', 'ident'),
        '.',
        'i8'
      )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node.right.right)).to.deep.equal(goal)
  })

  it('priority', () => {
    let
      src = 'const _ = -1 * un 2 + 3 and 4 + 5 * 6 + x.y',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = lr(
        lr(
          lr(sr('-x', 'i8'), '*', sr('un', 'i8')),
          '+',
          'i8'
        ),
        'and',
        lr(
          lr(
            'i8',
            '+',
            lr('i8', '*', 'i8')
          ),
          '+',
          lr('ident', '.', 'ident')
        )
      )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node.right.right)).to.deep.equal(goal)
  })

  it('multiple assignment', () => {
    let
      src = `const
      _, " ", _, "()" =
        -1, -2, "" + -iota++, (-1),
        [int, 1][]
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = lr(
        sb(',', ['_', 'ident', '_', 'ident']),
        '=',
        sb(',', [
          sr('-x', 'i8'),
          sr('-x', 'i8'),
          lr(
            'ident',
            '+',
            sr('-x', sl('++', 'iota'))
          ),
          sb('()', [sr('-x', 'i8')]),
          sl('[]', sb('[]', ['ident', 'i8']),  [])
        ])
      )
    expect(parse.error).to.be.equal(null)
    expect(node.sign).to.be.equal(sign.const)
    expect(node.body && node.body.length).to.be.equal(1)

    node = node.body[0]

    expect(tranform(node.left)).to.deep.equal(goal.left)
    expect(tranform(node.right)).to.deep.equal(goal.right)

  })

  it('list', () => {
    let
      src = 'const _ = -x.y[-i][1,(1+2)*3]\n  _ = 1',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      right =
        sr('-x',
          sl('[]',
            sl('[]',
              lr('ident', '.', 'ident'),
              [
                sr('-x', 'ident')
              ]
            ),
            [
              'i8',
              lr(
                sb('()', [lr('i8', '+', 'i8')]),
                '*', 'i8'
              )
            ]
          )
        )

    expect(parse.error).to.be.equal(null)
    expect(node.body.length).to.be.equal(1)

    expect(tranform(node.right.right)).to.deep.equal(right)
    expect(tranform(node.body[0].right)).to.deep.equal(si('i8'))
  })

  it('map', () => {
    let
      src = `const
      _ = {string} [
      null: b,
      1+2 : c + x,
      ]
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sl('[]',
          sb('{}', ['ident']),
          [
            lr('null', ':', 'ident'),
            lr(
              lr('i8', '+', 'i8'),
              ':',
              lr('ident', '+', 'ident')
            ),
          ]
        )

    expect(parse.error).to.be.equal(null)
    expect(tranform(node)).to.deep.equal(ignore(goal))

  })

  it('group', () => {
    let
      src = `const
      _
      =
      (x)(true)(1)
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sl('()',
          sl('()',
            sb('()',
              ['ident']
            ),
            ['true']
          ),
          ['i8']
        )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(ignore(goal))

  })

  it('line break before/after operator', () => {
    let
      src = `const
      _
      =
      -true
      .y
      [
        -1
        +
        i++
      ]--
      is {string}
      and x() + y
      () isnot true or
      un~x or
      not -1 *
      +2
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      chain =
        sr('-x',
          sl('--',
            sl('[]',
              lr('true', '.', 'ident'),
              [
                lr(
                  sr('-x', 'i8'),
                  '+',
                  sl('++', 'ident')
                )
              ]
            )
          )
        ),
      goal =
        lr(
          lr(
            lr(
              lr(chain, 'is', sb('{}', ['ident'])),
              'and',
              lr(
                lr(
                  sl('()', 'ident', []),
                  '+',
                  sl('()', 'ident', [])
                ),
                'isnot', 'true'
              )
            ),
            'or',
            sr('un', sr('~', 'ident'))
          ),
          'or',
          sr('not',
            lr(
              sr('-x', 'i8'), '*', sr('+x', 'i8')
            )
          )
        )

    expect(parse.error).to.be.equal(null)
    expect(node.body.length).to.be.equal(1)

    expect(tranform(node)).to.deep.equal(ignore(goal))
  })

})

describe('declaration', () => {

  it('use', () => {
    let
      src = `use
        'os' 1.0.0 _ [
          file, path
        ]
        ; note
        ---
        ---
        'io' 1.0.0-
        'x'  1.0.0-2.0.0
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sl('[]',
          sl('_',
            sr('string', 'version')
          ),
          ['ident', 'ident']
        ),
        sr('string', sb('version', ['-'])),
        sr('string', sb('version', ['-', 'version']))
      ]

    expect(parse.error).to.be.equal(null)
    expect(node.sign).to.be.equal(sign.use)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })

  it('type-of', () => {
    let
      src = 'type data of 1, int',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sr('type',
          lr('ident', 'of', sb(',', ['i8', 'ident']))
        )
    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)

  })


  it('accessibility', () => {
    let
      src = `type name
        x?  fn()
        y!? {int}
      `,
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sr('type',
          'ident',
          [
            sl('fn',
              sr('ident', '?'),
              [
                sb('()', [])
              ]
            ),
            sl('{}',
              sr('ident', sl('?', '!')),
              ['ident']
            ),
          ]
        )
    expect(parse.error).to.be.equal(null)
    fmt(node)
    expect(tranform(node)).to.deep.equal(goal)

  })

  it('fn', () => {
    let
      src =
        'extern fn x(int x, bool true, out this, super)\n' +
        '  out x, 1 if z',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sr('fn',
          sl('()',
            'ident',
            [
              lr(
                sb(',',
                  [
                    sl('ident', 'ident'),
                    sl('true', 'ident')
                  ]
                ),
                'out',
                sb(',',
                  [
                    'this', 'super'
                  ]
                )
              )
            ]
          ),
          [
            lr(
              sr('out', sb(',', ['ident', 'i8'])),
              'if', 'ident'
            )
          ]
        )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)
  })

  it('fn(){}', () => {
    let
      src =
        'fn x(int true) {\n' +
        '  out x\n}',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
      sr('fn',
        sl('()',
          'ident',
          [
            sl('true', 'ident')
          ]
        ),
        [
          sb('body',
            [
              sr('out', 'ident')
            ]
          )
        ]
      )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)

  })


  it('type', () => {
    let
      src = `
type i extends null trait true, string
  false true
  _ int
  false {int}

  `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sr('type',
          lr(
            lr(
              'ident',
              'extends',
              'null'
            ),
            'trait',
            sb(',', ['true', 'ident'])
          ),
          [
            sl('true', 'false'),
            sl('ident', '_'),
            sl('{}', 'false', ['ident']),

          ]
        )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)

  })

  it('block', () => {
    let
      src =
        'fn x()\n' +
        '  out fn(out int) {\n' +
        '    if true\n' +
        '      1++\n' +
        '    out int,9\n' +
        '  }',
      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal =
        sr('fn',
          sl('()', 'ident', []),
          [
            sr('out',
              sr('fn',
                sb('()',
                  [
                    sr('out', 'ident')
                  ]
                ),
                [
                  sb('body',
                    [
                      sr('if', 'true',
                        [
                          sl('++', 'i8')
                        ]
                      ),
                      sr('out', sb(',', ['ident', 'i8']))
                    ]
                  )
                ]
              )
            )
          ]
        )

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)

  })

})

describe('for', () => {
  it('for', () => {
    let
      src = `fn x()
        for x
          x--
        else
          out
        for x,8 of 1..10
          yield x, true
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sr('for', 'ident', [sl('--', 'ident')]),
        sb('else', ['out']),
        sr('for',
          lr(
            sb(',', ['ident', 'i8']),
            'of',
            lr('i8', '..', 'i8')
          ),
          [
            sr('yield', sb(',', ['ident', 'true']))
          ]
        )
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })

  it('for {}', () => {
    let
      src = `fn x()
        for x {
          x--
        } else
          out
        for x,8 of 1..10
        {
          yield x, true
        }
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sr('for', 'ident',
          [
            sb('body', [sl('--', 'ident')])
          ]
        ),
        sb('else', ['out']),
        sr('for',
          lr(
            sb(',', ['ident', 'i8']),
            'of',
            lr('i8', '..', 'i8')
          ),
          [
            sb('body', [
              sr('yield', sb(',', ['ident', 'true']))
            ])
          ]
        )
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })

})

describe('if', () => {
  it('if-else', () => {
    let
      src = `fn x()
        if true
          x--
        else if false
          x()
          x++
        elif 1
          x--
          x++
        else
          x++
          x--
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sr('if', 'true', [sl('--', 'ident')]),
        sr('else',
          sr('if', 'false'),
          [
            sl('()', 'ident', []),
            sl('++', 'ident')
          ]
        ),
        sr('elif',
          'i8',
          [
            sl('--', 'ident'),
            sl('++', 'ident')
          ]
        ),
        sb('else',
          [
            sl('++', 'ident'),
            sl('--', 'ident')
          ]
        )
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })

  it('if-else {}', () => {
    let
      src = `fn x()
        if true

        {  x-- }

        else if false {
          x()
          x++

        }

        elif 1

        {

          x--

          x++

        } else {
          x++
          x-- }
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sr('if', 'true',
          [
            sb('body', [sl('--', 'ident')])
          ]
        ),
        sr('else',
          sr('if', 'false'),
          [
            sb('body', [
              sl('()', 'ident', []),
              sl('++', 'ident')
            ])
          ]
        ),
        sr('elif',
          'i8',
          [
            sb('body', [
              sl('--', 'ident'),
              sl('++', 'ident')
            ])
          ]
        ),
        sb('else',
          [
            sb('body', [
              sl('++', 'ident'),
              sl('--', 'ident')
            ])
          ]
        )
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })
})

describe('try', () => {
  it('try', () => {
    let
      src = `fn x()
        try
          x--
        catch error
        throw 1, 'msg', any
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        sb('try', [ sl('--', 'ident') ]),
        sr('catch', 'ident'),
        sr('throw', sb(',', ['i8', 'string', 'ident']))
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })
})

describe('of', () => {
  it('of', () => {
    let
      src = `fn x()
        x, ok = int of y
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = [
        lr(
          sb(',', ['ident', 'ident']),
          '=',
          lr('ident', 'of', 'ident')
        )
      ]

    expect(parse.error).to.be.equal(null)

    expect(node.body.map(tranform)).to.deep.equal(goal)
  })
})

describe('@pegz', () => {
  it('should be work', () => {
    let
      src = `const @pegz
        Expr    = Value
        Value   = ['0'-'9']+
      `,

      parse = new Parser(new Lexer(src)),
      node = parse.section(),
      goal = {
        sign: 'const',
        right: {
          sign: '@',
        },
        body: [
          {sign:'ident'},
          {sign:'='},
          {sign:'ident'},
          {sign:'ident'},
          {sign:'='},
          {sign:'['},
          {sign:'string'},
          {sign:'-'},
          {sign:'string'},
          {sign:']'},
          {sign:'+'},
        ]
      }

    expect(parse.error).to.be.equal(null)

    expect(tranform(node)).to.deep.equal(goal)
  })
})

describe('multi-statements',  () => {
  it('should be work', () => {
    let
      src = 'type name\nfn name()',
      parse = new Parser(new Lexer(src)),
      node  = parse.section()

    expect(parse.error).to.be.equal(null)
    expect(node.sign).to.equal(sign.type)
    node = parse.section()
    expect(parse.error).to.be.equal(null)
    expect(node.sign).to.equal(sign.fn)
  })
})
